﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.Globalization;
using System.Xml.Linq;

namespace FakeDataGen
{
    /// <summary>
    /// Gera dados Falsos
    /// </summary>
    public class FakeData : IDisposable
    {
        private Random r = new Random();

        public string Locale { get; private set; }
        string[] strings;

        public bool CPFValido { get; set; }
        public bool CPFPontuado { get; set; }
        public int IdadeMinima { get; set; }
        public int IdadeMaxima { get; set; }
        public bool PhonePontuado { get; set; }

        /// <summary>
        /// Gera dados Falsos em Português do Brasil
        /// </summary>
        public FakeData()
        {
            Locale = "ptBR";

            LoadResource();

            SetInitialParameters();
        }

        /// <summary>
        /// Gera dados Falsos no idioma desejado
        /// </summary>
        /// <param name="locale"></param>
        public FakeData(string locale)
        {
            if (locale.Length == 5)
                locale = locale.Remove(2, 1);

            Locale = locale;

            LoadResource();

            SetInitialParameters();
        }

        private void SetInitialParameters()
        {
            IdadeMaxima = 100;
        }

        private void LoadResource()
        {
            strings = Properties.Resources.ResourceManager.GetString(Locale).Split('\n');
        }
        
        #region [ generators ]
        private FakeAddress FakeAddress()
        {
            FakeAddress address = new FakeAddress();

            string[] tipos = strings[5].Split(',');
            string[] logradouros = strings[6].Split(',');

            List<KeyValuePair<string, string[]>> lista = new List<KeyValuePair<string, string[]>>();
            string[] estados = strings[7].Split(':');

            for (int i = 0; i < estados.Length; i++)
            {
                string estadoatual = estados[i].Split(',')[0];
                string[] cidades = estados[i].Split(',').Skip(1).ToArray();

                lista.Add(new KeyValuePair<string, string[]>(estadoatual, cidades));
            }

            string logradouro = String.Empty;

            if (r.NextDouble() > 0.5)
                logradouro = FakeName(r.NextDouble() > 0.5);
            else
                logradouro = logradouros[r.Next(logradouros.Length - 1)];

            StringBuilder sb = new StringBuilder();
            string tipo = tipos[r.Next(tipos.Length - 1)];
            if (Locale == "enUS")
            {
                sb.Append(logradouro);
                sb.Append(" ");
                sb.Append(tipo);
            }
            else
            {
                sb.Append(tipo);
                sb.Append(" ");
                sb.Append(logradouro);
            }

            string rua = sb.ToString();
            int numero = r.Next(999);

            int rand = r.Next(lista.Count);
            string cidade = lista[rand].Value[r.Next(0, lista[rand].Value.Length)];
            string estado = lista[rand].Key;


            address.Street = rua;
            address.Number = numero;
            address.City = cidade;
            address.State = estado;

            return address;
        }

        private int FakeAge(int min, int max)
        {
            if (min < 0)
                throw new Exception("A idade deve ser positiva.");
            else
                return r.Next(min, max);
        }

        private string FakeCPF(bool valido, bool pontuado)
        {
            byte mult = 9;
            int[] n = new int[9];
            int d1 = 0;
            int d2 = 0;

            n[0] = r.Next(mult);
            n[1] = r.Next(mult);
            n[2] = r.Next(mult);
            n[3] = r.Next(mult);
            n[4] = r.Next(mult);
            n[5] = r.Next(mult);
            n[6] = r.Next(mult);
            n[7] = r.Next(mult);
            n[8] = r.Next(mult);

            if (valido)
            {
                d1 = n[8] * 2 + n[7] * 3 + n[6] * 4 + n[5] * 5 + n[4] * 6 + n[3] * 7 + n[2] * 8 + n[1] * 9 + n[0] * 10;
                d1 = 11 - (d1 % 11);
                if (d1 >= 10)
                    d1 = 0;
                d2 = d1 * 2 + n[8] * 3 + n[7] * 4 + n[6] * 5 + n[5] * 6 + n[4] * 7 + n[3] * 8 + n[2] * 9 + n[1] * 10 + n[0] * 11;
                d2 = 11 - (d2 % 11);
                if (d2 >= 10)
                    d2 = 0; 
            }

            if (pontuado == true)
                return n[0] + n[1] + n[2] + "." + n[3] + n[4] + n[5] + "." + n[6] + n[7] + n[8] + "-" + d1 + d2;
            else
                return n[0].ToString() + n[1] + n[2] + n[3] + n[4] + n[5] + n[6] + n[7] + n[8] + d1 + d2;
        }

        private DateTime FakeDate(int min, int max)
        {
            DateTime data;

            do
            {
                data = new DateTime(DateTime.Today.Year - max, 01, 01);
                int intervalo = ((TimeSpan)(DateTime.Today - data)).Days;
                data = data.AddDays(r.Next(intervalo));
            } while ((DateTime.Today.Year - data.Year) < min);

            return data;
        }

        private String FakeName(bool isEndereco)
        {
            int rand;
            List<int> nsorteados = new List<int>();
            List<int> snsorteados = new List<int>();

            string[] nomesh = strings[0].Split(',');
            string[] nomesm = strings[1].Split(',');
            string[] sobrenomes = strings[2].Split(',');
            string[] titulos = strings[3].Split(',');

            int qnomes = r.Next(1, 3);
            int qsobrenomes = r.Next(1, 3);

            string[] nomes = nomesh;
            if (!isEndereco && r.NextDouble() > 0.5)
                nomes = nomesm;

            StringBuilder sb = new StringBuilder();

            if (isEndereco)
            {
                rand = r.Next(titulos.Length - 1);
                sb.Append(titulos[rand]);
                sb.Append(" ");
            }

            for (int i = 0; i < qnomes; i++)
            {
                do
                    rand = r.Next(nomes.Length - 1);
                while (nsorteados.Contains(rand));

                if (i > 0)
                    sb.Append(" ");

                sb.Append(nomes[rand]);
                nsorteados.Add(rand);
            }

            for (int i = 0; i < qsobrenomes; i++)
            {
                do
                    rand = r.Next(sobrenomes.Length - 1);
                while (snsorteados.Contains(rand));

                sb.Append(" ");

                sb.Append(sobrenomes[rand]);
                snsorteados.Add(rand);
            }

            return sb.ToString();
        }

        private string FakePhoneNumber(bool pontuado)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < 10; i++)
            {
                sb.Append(r.Next(0, 9));
            }

            if (pontuado == true)
            {
                return "(" + sb[0] + sb[1] + ")" + sb[2] + sb[3] + sb[4] + sb[5] + "-" + sb[6] + sb[7] + sb[8] + sb[9];
            }
            else
            {
                return sb[0].ToString() + sb[1]+ sb[2] + sb[3] + sb[4] + sb[5]+ sb[6] + sb[7] + sb[8] + sb[9];
            }
        }

        private string FakeEmail(string nome, bool completo)
        {
            StringBuilder sb = new StringBuilder();
            string[] partes = nome.Split(' ');
            int sobrenomecompleto = r.Next(partes.Length - 1);

            string[] provedores = strings[4].Split(',');

            for (int i = 0; i < partes.Length; i++)
            {
                if (completo)
                    sb.Append(partes[i]);
                else
                {
                    if (i == sobrenomecompleto & partes[i].Length > 2)
                        sb.Append(partes[i]);
                    else if (partes[i].Length > 2)
                        sb.Append(partes[i][0]);
                }
            }

            sb.Append("@");
            sb.Append(provedores[r.Next(provedores.Length - 1)]);

            String normalizada = sb.ToString().Normalize(NormalizationForm.FormD);
            sb.Clear();
            for (int i = 0; i < normalizada.Length; i++)
            {
                Char c = normalizada[i];
                if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
                    sb.Append(c);
            }

            return sb.ToString().ToLower(); ;
        }
        #endregion
        
        /// <summary>
        /// Gera um Endereço falso
        /// </summary>
        /// <returns>O endereço completo em um objeto <c>FakeAddress</c></returns>
        public FakeAddress Address()
        {
            return FakeAddress();
        }

        /// <summary>
        /// Gera uma Idade falsa com no máximo 100 anos
        /// </summary>
        /// <param name="min">A idade mínima</param>
        /// <returns>Uma idade aleatória</returns>
        public int Age(int min)
        {
            return FakeAge(min, 100);
        }

        /// <summary>
        /// Gera uma Idade falsa
        /// </summary>
        /// <param name="min">A idade mínima</param>
        /// <param name="max">A idade máxima</param>
        /// <returns>Uma idade aleatória</returns>
        public int Age(int min, int max)
        {
            return FakeAge(min, max);
        }

        /// <summary>
        /// Gera um CPF válido e pontuado
        /// </summary>
        /// <returns>Um número válido de CPF já pontuado</returns>
        public string CPF()
        {
            return FakeCPF(true, true);
        }

        /// <summary>
        /// Gera um CPF não pontuado
        /// </summary>
        /// <param name="valido">Se o CPF a ser gerado é válido ou não</param>
        /// <returns>Um número de CPF não pontuado</returns>
        public string CPF(bool valido)
        {
            return FakeCPF(valido, false);
        }

        /// <summary>
        /// Gera um CPF
        /// </summary>
        /// <param name="valido">Se o CPF a ser gerado é válido ou não</param>
        /// <param name="pontuado">Se o CPF gerado possui pontos e traço</param>
        /// <returns>Um número de CPF</returns>
        public string CPF(bool valido, bool pontuado)
        {
            return FakeCPF(valido, pontuado);
        }

        /// <summary>
        /// Gera uma Data falsa
        /// </summary>
        /// <returns>A data com no máximo 100 anos</returns>
        public DateTime Date()
        {
            return FakeDate(0, 100);
        }

        /// <summary>
        /// Gera uma Data falsa
        /// </summary>
        /// <param name="idademinima">A quantidade mínima de anos</param>
        /// <returns>A data com máximo 100 anos e o mínimo definido por parâmetro</returns>
        public DateTime Date(int idademinima)
        {
            return FakeDate(idademinima, 100);
        }

        /// <summary>
        /// Gera uma Data falsa
        /// </summary>
        /// <param name="idademinima">A quantidade mínima de anos</param>
        /// <param name="idademaxima">A quantidade máxima de anos</param>
        /// <returns>A data com o mínimo e o máximo definidos por parâmetro</returns>
        public DateTime Date(int idademinima, int idademaxima)
        {
            return FakeDate(idademinima, idademaxima);
        }

        /// <summary>
        /// Gera um Email falso
        /// </summary>
        /// <param name="nome">O nome completo da pessoa</param>
        /// <returns>O email baseado no nome</returns>
        public string Email(string nome)
        {
            return FakeEmail(nome, true);
        }

        /// <summary>
        /// Gera um Email falso
        /// </summary>
        /// <param name="nome">O nome completo da pessoa</param>
        /// <param name="completo">Se o email gerado será formado pelo nome completo</param>
        /// <returns>O email baseado no nome</returns>
        public string Email(string nome, bool completo)
        {
            return FakeEmail(nome, completo);
        }

        /// <summary>
        /// Gera um nome aleatório
        /// </summary>
        /// <returns>Uma string contendo o nome</returns>
        public String Name()
        {
            return FakeName(false);
        }

        /// <summary>
        /// Gera um Telefone Falso
        /// </summary>
        /// <returns>O telefone</returns>
        public string PhoneNumber()
        {
            return FakePhoneNumber(true);
        }

        /// <summary>
        /// Gera um Telefone Falso
        /// </summary>
        /// <param name="pontuado">Define se o retorno será pontuado ou não</param>
        /// <returns>O telefone com ou sem pontuação</returns>
        public string PhoneNumber(bool pontuado)
        {
            return FakePhoneNumber(pontuado);
        }

        /// <summary>
        /// Gera um XML com os dados fictícios
        /// </summary>
        /// <param name="q">A quantidade de entradas</param>
        /// <param name="types">As informações a serem geradas e sua ordem exata</param>
        /// <returns>Um XML em memória contendo os dados formatados na ordem desejada, mas com os elementos raiz e de ítem padrão. Os nomes das chaves serão obtidos automaticamente em inglês.</returns>
        public XDocument XML(int q, params FakeDataType[] types)
        {
            KeyValuePair<String, FakeDataType>[] tipos = new KeyValuePair<String, FakeDataType>[types.Length];

            for (int i = 0; i < types.Length; i++)
            {
                tipos[i] = new KeyValuePair<String, FakeDataType>(types[i].ToString(), types[i]);
            }

            return FakeXML(q, "items", "item", tipos);
        }

        /// <summary>
        /// Gera um XML com os dados fictícios
        /// </summary>
        /// <param name="q">A quantidade de entradas</param>
        /// <param name="rootname">O nome do elemento raiz do XML</param>
        /// <param name="itemname">O nome do elemento que representa cada entrada</param>
        /// <param name="types">As informações a serem geradas e sua ordem exata</param>
        /// <returns>Um XML em memória contendo os dados formatados como desejado. Os nomes das chaves serão obtidos automaticamente em inglês.</returns>
        public XDocument XML(int q, String rootname, String itemname, params FakeDataType[] types)
        {
            KeyValuePair<String, FakeDataType>[] tipos = new KeyValuePair<String, FakeDataType>[types.Length];

            for (int i = 0; i < types.Length; i++)
            {
                tipos[i] = new KeyValuePair<String, FakeDataType>(types[i].ToString(), types[i]);
            }

            return FakeXML(q, rootname, itemname, tipos);
        }

        /// <summary>
        /// Gera um XML com os dados fictícios
        /// </summary>
        /// <param name="q">A quantidade de entradas</param>
        /// <param name="rootname">O nome do elemento raiz do XML</param>
        /// <param name="itemname">O nome do elemento que representa cada entrada</param>
        /// <param name="types">As informações a serem geradas e sua ordem exata. O nome da chave deve ser passado em Key e o tipo de dado em Value</param>
        /// <returns>Um XML em memória contendo os dados formatados como desejado.</returns>
        public XDocument XML(int q, String rootname, String itemname, params KeyValuePair<String, FakeDataType>[] types)
        {
            return FakeXML(q, rootname, itemname, types);
        }

        /// <summary>
        /// Gera um script SQL com os dados fictícios
        /// </summary>
        /// <param name="q">A quantidade de entradas</param>
        /// <param name="tablename">O nome da tabela que receberá os dados</param>
        /// <param name="types">As informações a serem geradas e sua ordem exata. O tipo de dado em Value</param>
        /// <returns>Um script SQL contendo os dados formatados como desejado</returns>
        public String SQL(int q, String tablename, params FakeDataType[] types)
        {
            KeyValuePair<String, FakeDataType>[] tipos = new KeyValuePair<String, FakeDataType>[types.Length];

            for (int i = 0; i < types.Length; i++)
            {
                tipos[i] = new KeyValuePair<String, FakeDataType>(types[i].ToString(), types[i]);
            }

            return FakeSQL(q, tablename, false, tipos);
        }
        /// <summary>
        /// Gera um script SQL com os dados fictícios
        /// </summary>
        /// <param name="q">A quantidade de entradas</param>
        /// <param name="tablename">O nome da tabela que receberá os dados</param>
        /// <param name="types">As informações a serem geradas e sua ordem exata. O nome da coluna no banco deve ser passado em Key e o tipo de dado em Value</param>
        /// <returns>Um script SQL contendo os dados formatados como desejado</returns>
        public String SQL(int q, String tablename, params KeyValuePair<String, FakeDataType>[] types)
        {
            return FakeSQL(q, tablename, true, types);
        }

        /// <summary>
        /// Gera um arquivo CSV com os dados fictícios
        /// </summary>
        /// <param name="q">A quantidade de entradas</param>
        /// <param name="types">As informações a serem geradas e sua ordem exata.</param>
        /// <returns>Um arquivo CSV (texto separado por ponto-e-vírgula) contendo os dados formatados como desejado</returns>
        public String CSV(int q, params FakeDataType[] types)
        {
            return FakeCSV(q, types);
        }

        #region [ tipos de retorno ]
        private XDocument FakeXML(int q, String rootname, String itemname, params KeyValuePair<String, FakeDataType>[] types)
        {
            XDocument doc = new XDocument();
            XElement root = new XElement(rootname.ToLower());
            doc.Add(root);

            for (int i = 0; i < q; i++)
			{
                String name = FakeName(false);

                XElement xele = new XElement(itemname.ToLower());

                for (int j = 0; j < types.Length; j++)
                {
                    XElement xele2 = null;
                    String xname = types[j].Key.ToLower();

                    xele2 = new XElement(xname, SwitchType(types[j].Value, name));
                    xele.Add(xele2);
                }
                root.Add(xele);
			}

            return doc;
        }

        private String FakeSQL(int q, String tablename, bool fullstatement, params KeyValuePair<String, FakeDataType>[] types)
        {
            StringBuilder sb1 = new StringBuilder();
            StringBuilder sb2 = new StringBuilder();

            for (int i = 0; i < q; i++)
            {
                String name = FakeName(false);

                sb1.Append("INSERT INTO ");
                sb1.Append(tablename);

                if (fullstatement)
                    sb1.Append(" (");

                for (int j = 0; j < types.Length; j++)
                {
                    String value = String.Empty;

                    if (fullstatement)
                    {
                        String columnname = types[j].Key.ToLower();

                        if (j > 0)
                            sb1.Append(", ");
                        sb1.Append(columnname);
                    }

                    if (j > 0)
                        sb2.Append(", ");

                    value = SwitchType(types[j].Value, name);

                    sb2.Append("\"");
                    sb2.Append(value);
                    sb2.Append("\"");
                }


                if (fullstatement)
                    sb1.Append(")");

                sb1.Append(" VALUES(");
                sb1.Append(sb2);
                sb1.Append(");");
                sb1.AppendLine();
                sb2.Clear();
            }

            return sb1.ToString();
        }

        private String FakeCSV(int q, params FakeDataType[] types)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < q; i++)
            {
                String name = FakeName(false);

                for (int j = 0; j < types.Length; j++)
                {
                    String value = String.Empty;

                    value = SwitchType(types[j], name);

                    if (j > 0)
                        sb.Append(";");

                    sb.Append(value);
                }
                sb.AppendLine();
            }

            return sb.ToString();
        }

        private String SwitchType(FakeDataType type, String name)
        {
            String value = String.Empty;
            switch (type)
            {
                case FakeDataType.Address:
                    value = FakeAddress().ToString();
                    break;
                case FakeDataType.Age:
                    value = FakeAge(IdadeMinima, IdadeMaxima).ToString();
                    break;
                case FakeDataType.CPF:
                    value = FakeCPF(CPFValido, CPFPontuado);
                    break;
                case FakeDataType.Date:
                    value = FakeDate(IdadeMinima, IdadeMaxima).ToString();
                    break;
                case FakeDataType.Email:
                    value = FakeEmail(name, false);
                    break;
                case FakeDataType.Name:
                    value = name;
                    break;
                case FakeDataType.Phone:
                    value = FakePhoneNumber(PhonePontuado);
                    break;
                default:
                    break;
            }
            return value;
        }
        #endregion

        #region [ disposing ]
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            r = null;
            Locale = null;
            strings = null;
        }
        #endregion
    }
}
